﻿#include "TapData.h"

TapData::TapData(const std::string& dev, std::function<void(DataCache)> func):
    Event(0),
    m_socket(-1),
    m_ifname(dev),
    m_func(func)
{
}

TapData::~TapData()
{
    shutdown();
}

int TapData::init()
{
    if (IsStart()) return -1;

    std::string _ifname = GetIfByMac(m_ifname);
    if ("" == _ifname)
    {
        LOGFMTE(
            "mac --- > ifname trans fail mac : %s", m_ifname.c_str()
        );
        return -1;
    }

    m_socket = tun_alloc(_ifname, IFF_TAP | IFF_NO_PI);
    if (m_socket < 0)
    {
        LOGFMTE(
            "tun_alloc error : %s:%d:%s", 
            _ifname.c_str(), m_socket, strerror(errno));
        return -1;
    }

    if (0 != this->start())
    {
        LOGFMTE("Virtual start thread fail");
        close(m_socket);

        return -1;
    }

    return 0;
}

int TapData::shutdown()
{
    if (!IsStart()) return -1;
    
    close(m_socket);
    return this->stop();
}

int TapData::VSendData(DataCache& data, int msecond)
{
    struct timeval tmv;
    fd_set w;

    FD_ZERO(&w);
    FD_SET(m_socket, &w);

    tmv.tv_sec = msecond / 1000;
    tmv.tv_usec = (msecond - tmv.tv_sec * 1000) * 1000;

    int ret = select(m_socket + 1, NULL, &w, NULL, &tmv);

    if (ret == 0) return 0;
    else if (ret < 0) return -1;

    return send_data(data(), data.size());
}

void TapData::CycleTask()
{
    int len = 2048, recvLen, ret;
    DataCache data(len);

    struct timeval tmv = { 2, 0 };
    fd_set r;

    FD_ZERO(&r);
    FD_SET(m_socket, &r);

    ret = select(m_socket + 1, &r, NULL, NULL, &tmv);

again_select:
    if (ret < 0)   /* on error */
    {
        if (errno == EINTR)  /* 系统中断，重新读 */
            goto again_select;

        LOGFMTE("select exec fail : %s", strerror(errno));
        return;
    }
    else if (ret == 0)
    {
        return;
    }

    if (!FD_ISSET(m_socket, &r)) return;

    recvLen = read(m_socket, data(), len);
    if (recvLen <= 0) return;
    data.set(recvLen);

    if (m_func)m_func(std::move(data));
}

int TapData::send_data(const char* buff, int bufflen)
{
    long lWrite, lLeft, lOffset;

    lLeft = bufflen;
    lOffset = 0;

    while (lLeft > 0)
    {
    again_write:
        lWrite = write(m_socket, buff + lOffset, lLeft);

        if (lWrite < 0)
        {
            if (errno == EINTR)
                goto again_write;

            return -1;
        }
        else if (lWrite == 0)
        {
            return -1;
        }

        lOffset += lWrite;
        lLeft -= lWrite;
    }

    return lOffset;
}

int TapData::tun_alloc(const std::string& dev, int flags) {

    struct ifreq ifr;
    int fd, err;
    const char* clonedev = "/dev/net/tun";

    /* Arguments taken by the function:
     *
     * char *dev: the name of an interface (or '\0'). MUST have enough
     *   space to hold the interface name if '\0' is passed
     * int flags: interface flags (eg, IFF_TUN etc.)
     */

     /* open the clone device */
    if ((fd = open(clonedev, O_RDWR)) < 0) 
    {
        LOGFMTE(
            "open virtual if fail : %s : %s", 
            dev.c_str(), strerror(errno)
        );
        return fd;
    }

    /* preparation of the struct ifr, of type "struct ifreq" */
    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = flags;   /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */

    if (!dev.empty()) {
        /* if a device name was specified, put it in the structure; otherwise,
         * the kernel will try to allocate the "next" device of the
         * specified type */
        strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ);
    }

    /* try to create the device */
    if ((err = ioctl(fd, TUNSETIFF, (void*)&ifr)) < 0) {
        LOGFMTE("ioctl fail : %s : %s", dev.c_str(), strerror(errno));
        close(fd);
        return err;
    }

    /* this is the special file descriptor that the caller will use to talk
     * with the virtual interface */
    return fd;
}